﻿using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Threading;

namespace FirstFloor.P2P
{
    /// <summary>
    /// Provides access to the Live Messenger P2P network.
    /// </summary>
    public class P2PApplication
    {
        /// <summary>
        /// Occurs when the application state has changed.
        /// </summary>
        public event EventHandler<P2PApplicationStateEventArgs> StateChanged;
        /// <summary>
        /// Occurs when the connection type has changed.
        /// </summary>
        public event EventHandler<P2PConnectionTypeEventArgs> ConnectionTypeChanged;
        /// <summary>
        /// Occurs when sending data has failed.
        /// </summary>
        public event EventHandler<P2PSendDataFailedEventArgs> SendDataFailed;
        /// <summary>
        /// Occurs when data has been received.
        /// </summary>
        public event EventHandler<P2PValueEventArgs> DataReceived;
        /// <summary>
        /// Occurs when an instant message has been received.
        /// </summary>
        public event EventHandler<P2PValueEventArgs> IMReceived;
        /// <summary>
        /// Occurs when the entire application is about to shutdown.
        /// </summary>
        public event EventHandler<EventArgs> Shutdown;

        private static P2PApplication current;

        private P2PApplicationState state;
        private P2PConnectionType connectionType;
        private P2PUserList users;
        private DispatcherTimer timeout;
        private P2PInterop interop;
        private bool connectInvoked = false;

        /// <summary>
        /// Initializes the <see cref="P2PApplication"/> class.
        /// </summary>
        static P2PApplication()
        {
            current = new P2PApplication();
        }

        private P2PApplication()
        {
            this.state = P2PApplicationState.Closed;
            this.connectionType = P2PConnectionType.Disconnected;
            this.users = new P2PUserList();

            this.timeout = new DispatcherTimer();
            this.timeout.Tick += timeout_Tick;
            this.timeout.Interval = TimeSpan.FromSeconds(10);

            this.interop = new P2PInterop();
        }

        private void timeout_Tick(object sender, EventArgs e)
        {
            OnClose(P2PApplicationCloseReason.ConnectTimeout);
        }

        internal void OnConnect()
        {
            if (this.state == P2PApplicationState.Connecting) {
                this.timeout.Stop();
                OnStateChanged(P2PApplicationState.Connected, P2PApplicationCloseReason.None);
            }
            else {
            }
        }

        internal void OnClose(P2PApplicationCloseReason reason)
        {
            if (this.state != P2PApplicationState.Closed) {
                this.timeout.Stop();
                this.users.Clear();

                if (reason == P2PApplicationCloseReason.ClosedByLocalApplication ||
                    reason == P2PApplicationCloseReason.ConnectTimeout) {
                    this.interop.Close();
                }

                OnConnectionTypeChanged(P2PConnectionType.Disconnected);
                OnStateChanged(P2PApplicationState.Closed, reason);
            }
        }

        internal void OnConnectionTypeChanged(P2PConnectionType connectionType)
        {
            if (this.connectionType != connectionType) {
                this.connectionType = connectionType;
                if (ConnectionTypeChanged != null) {
                    ConnectionTypeChanged(this, new P2PConnectionTypeEventArgs(connectionType));
                }
            }
        }

        internal void OnShutdown()
        {
            if (Shutdown != null) {
                Shutdown(this, EventArgs.Empty);
            }
        }

        internal void OnSendDataFailed(int type, string data)
        {
            if (this.state == P2PApplicationState.Connected && SendDataFailed != null) {
                SendDataFailed(this, new P2PSendDataFailedEventArgs(type, data));
            }
        }

        internal void OnDataReceived(string data)
        {
            if (this.state == P2PApplicationState.Connected && DataReceived != null) {
                DataReceived(this, new P2PValueEventArgs(data));
            }
        }

        internal string OnIMReceived(string im)
        {
            if (this.state == P2PApplicationState.Connected && IMReceived != null) {
                P2PValueEventArgs e = new P2PValueEventArgs(im);
                IMReceived(this, e);

                im = e.Value;
            }

            return im;
        }

        private void OnStateChanged(P2PApplicationState state, P2PApplicationCloseReason reason)
        {
            this.state = state;

            if (StateChanged != null) {
                StateChanged(this, new P2PApplicationStateEventArgs(state, reason));
            }
        }

        /// <summary>
        /// Gets the application state.
        /// </summary>
        /// <value>The state.</value>
        public P2PApplicationState State
        {
            get { return this.state; }
        }

        /// <summary>
        /// Gets a value indicating whether the P2P network is available.
        /// </summary>
        /// <value>The last close reason.</value>
        public bool P2PNetworkAvailable
        {
            get { return this.interop.P2PNetworkAvailable; }
        }

        /// <summary>
        /// Gets the type of the connection.
        /// </summary>
        /// <value>The type.</value>
        public P2PConnectionType ConnectionType
        {
            get { return this.connectionType; }
        }

        /// <summary>
        /// Gets or sets the connect timeout.
        /// </summary>
        /// <value>The connect timeout.</value>
        /// <remarks>Setting the timeout is only effective when invoked before connecting.</remarks>
        public TimeSpan ConnectTimeout
        {
            get { return this.timeout.Interval; }
            set
            {
                if (!this.connectInvoked) {
                    this.timeout.Interval = value;
                }
            }
        }

        /// <summary>
        /// Gets the list of participating users.
        /// </summary>
        /// <value>The local user.</value>
        public P2PUserList Users
        {
            get { return this.users; }
        }

        /// <summary>
        /// Connects the application. Make sure you check the P2PNetworkAvailable property before connecting.
        /// </summary>
        /// <remarks>You can only connect once.</remarks>
        public void Connect()
        {
            if (!this.P2PNetworkAvailable) {
                throw new InvalidOperationException("Failed to connect, the P2P network not available. Are you sure you running the application in the Live Messenger?");
            }
            if (this.connectInvoked) {
                throw new InvalidOperationException("Connect can only be invoked once.");
            }
            this.connectInvoked = true;

            OnStateChanged(P2PApplicationState.Connecting, P2PApplicationCloseReason.None);

            this.timeout.Start();
            this.interop.Connect();
        }

        /// <summary>
        /// Closes the application. Effectively closes the Activity Window in the Live Messenger.
        /// </summary>
        public void Close()
        {
            OnClose(P2PApplicationCloseReason.ClosedByLocalApplication);
        }

        /// <summary>
        /// Enters an instant message.
        /// </summary>
        /// <param name="im">The im.</param>
        public void EnterIM(string im)
        {
            this.interop.EnterIM(im);
        }

        /// <summary>
        /// Sends data to the remote party.
        /// </summary>
        /// <param name="data">The data.</param>
        public void SendData(string data)
        {
            this.interop.SendData(data);
        }

        /// <summary>
        /// Sends an instant message.
        /// </summary>
        /// <param name="im">The im.</param>
        public void SendIM(string im)
        {
            this.interop.SendIM(im);
        }

        /// <summary>
        /// Sends an instant message that appears to come from the user.
        /// </summary>
        /// <param name="im">The im.</param>
        public void SendIMAsUser(string im)
        {
            this.interop.SendIMAsUser(im);
        }

        /// <summary>
        /// Gets the P2PApplication instance for the current application.
        /// </summary>
        /// <value>The current.</value>
        public static P2PApplication Current
        {
            get { return current; }
        }
    }
}
